﻿using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace BrandonHaynes.Data
	{
	public static class DataReaderCodeGenerator
		{
		public static CodeCompileUnit GenerateDataReaders(IEnumerable<IEntity> entities)
			{
			if (entities == null)
				throw new ArgumentNullException("entities");

			var unit = new CodeCompileUnit();

			// namespace BrandonHaynes.Data.DataReaders
			var ns = new CodeNamespace("BrandonHaynes.Data.DataReaders");

			var typeDeclarations = entities.Select(entity => GenerateTypeDeclaration(entity));

			unit.Namespaces.Add(ns);

			foreach (var typeDeclaration in typeDeclarations)
				ns.Types.Add(typeDeclaration);

			return unit;
			}

		public static CodeTypeDeclaration GenerateTypeDeclaration(IEntity entity)
			{
			//		class TabDataReader
			var typeDeclaration = new CodeTypeDeclaration(entity.Name + "DataReader");

			//			private static IList<string> MemberList = new string[] { ... };
			var staticListInitExpression = new CodeArrayCreateExpression(typeof(string[]),
				entity.Properties
					.Select(member => new CodePrimitiveExpression(member.Name))
					.ToArray());
			var staticList = new CodeMemberField(typeof(IList<string>), "MemberList");

			//			public TypedLinqToSqlDataReader(IEnumerable<GetTabsResult> entities)
			//				: base(entities)
			//				{ }
			var constructor = new CodeConstructor();
			var constructorArgument = new CodeParameterDeclarationExpression(string.Format(CultureInfo.InvariantCulture, "System.Collections.Generic.IEnumerable<{0}>", entity.Name), "entities");
			var constructorBaseArgument = new CodeVariableReferenceExpression("entities");

			//			protected override IList<string> Members
			//				{ get { return MemberList; } }
			var memberProperty = new CodeMemberProperty();
			var memberPropertyType = new CodeTypeReference(typeof(IList<string>));
			var memberPropertyStatement = new CodeMethodReturnStatement(new CodeFieldReferenceExpression(null, "MemberList"));

			//			public override object this[string name]
			//				{
			//				get 
			//					{
			//					string upperName = name.ToUpperInvariant();
			//					if(upperName.Equals("XXX"))
			//						return (object)Adaptee.XXX ?? DBNull.Value;
			//					...
			//					throw new KeyNotFoundException();
			//					}
			//}
			var indexProperty = new CodeMemberProperty();
			var indexPropertyType = new CodeTypeReference(typeof(object));

			var loweredDeclaration = new CodeVariableDeclarationStatement(typeof(string), "upperName",
				new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("name"), "ToUpperInvariant"));

			var ifStatements = GenerateIfStatements(entity);

			var throwStatement = new CodeThrowExceptionStatement(
				new CodeObjectCreateExpression(
					new CodeTypeReference(typeof(KeyNotFoundException)),
					new CodeExpression[] { }));

			// Roll up type declaration tree
			typeDeclaration.Attributes = MemberAttributes.Public;
			typeDeclaration.BaseTypes.Add(string.Format(CultureInfo.InvariantCulture, "BrandonHaynes.Data.TypedDataReaderAdapterBase<{0}>", entity.Name));
			typeDeclaration.Members.Add(staticList);
			typeDeclaration.Members.Add(constructor);
			typeDeclaration.Members.Add(indexProperty);
			typeDeclaration.Members.Add(memberProperty);

			staticList.Attributes = MemberAttributes.Private | MemberAttributes.Static;
			staticList.InitExpression = staticListInitExpression;

			constructor.Attributes = MemberAttributes.Public;
			constructor.Parameters.Add(constructorArgument);
			constructor.BaseConstructorArgs.Add(constructorBaseArgument);

			memberProperty.Name = "Members";
			memberProperty.Attributes = MemberAttributes.Override | MemberAttributes.Family;
			memberProperty.Type = memberPropertyType;
			memberProperty.GetStatements.Add(memberPropertyStatement);

			indexProperty.Name = "Item";
			indexProperty.Attributes = MemberAttributes.Public | MemberAttributes.Override;
			indexProperty.Type = indexPropertyType;
			indexProperty.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name"));
			indexProperty.GetStatements.Add(loweredDeclaration);
			indexProperty.GetStatements.AddRange(ifStatements.ToArray());
			indexProperty.GetStatements.Add(throwStatement);

			return typeDeclaration;
			}

		private static IEnumerable<CodeStatement> GenerateIfStatements(IEntity entity)
			{
			var statementList = new List<CodeStatement>();

			//					if(upperName.Equals([Property Name]))
			//						[Property Invocation Statement];
			foreach (var property in entity.Properties)
				statementList.Add(new CodeConditionStatement(
					new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("upperName"), "Equals", new CodePrimitiveExpression(property.Name.ToUpperInvariant())),
					property.InvokeCode));

			return statementList;
			}
		}
	}
